#include "platform.h"
#include "renderobject.h"
#include "applicationengine.h"

using namespace NITK;
RenderObjectData::~RenderObjectData()
{
    if (layout)
        layout->removeRenderObject(renderObject);
    delete image;
}

RenderObject::RenderObject(Type type, unsigned int color)
{
    assert(Platform::instance());
    assert(Platform::instance()->applicationEngine());
    Platform::instance()->applicationEngine()->data.renderObjects.insert(this);
    init(this, type);
    setBackground(color);
}

RenderObject::RenderObject(Type type, Image *image)
{
    assert(Platform::instance());
    assert(Platform::instance()->applicationEngine());
    Platform::instance()->applicationEngine()->data.renderObjects.insert(this);
    init(this, type);
    setBackground(image);
}

RenderObject::RenderObject(Type type, int r, int g, int b, int a)
{
    assert(Platform::instance());
    assert(Platform::instance()->applicationEngine());
    Platform::instance()->applicationEngine()->data.renderObjects.insert(this);
    init(this, type);
    setBackground(r, g, b, a);
}

RenderObject::RenderObject(Type type, const std::string &pathToImage)
{
    assert(Platform::instance());
    assert(Platform::instance()->applicationEngine());
    Platform::instance()->applicationEngine()->data.renderObjects.insert(this);
    init(this, type);
    setBackground(pathToImage);
}

RenderObject::~RenderObject()
{
    if (!data->deleteOverride) {
        assert(Platform::instance());
        assert(Platform::instance()->applicationEngine());
        Platform::instance()->applicationEngine()->data.renderObjects.insert(this);
    }
    delete data;
}

RenderObject::Type RenderObject::type() const
{
    return static_cast<Type>(data->type);
}

void RenderObject::setBackground(unsigned int color)
{
    delete data->image;
    data->image = 0;
    data->background = color;
    update();
}

void RenderObject::setForeground(unsigned int color)
{
    data->foreground = color;
    update();
}


void RenderObject::update()
{
    Platform::instance()->update(this);
}

Image *RenderObject::image() const
{
    return data->image;
}

unsigned int RenderObject::background() const
{
    return data->background;
}

unsigned int RenderObject::foreground() const
{
    return data->foreground;
}

void RenderObject::setGeometry(const Rectangle &rectangle)
{
    if (rectangle != data->geometry) {
        data->geometry = rectangle;
        geometryChanged();
    }
}

double RenderObject::zValue() const
{
    return data->zValue;
}

void RenderObject::setZValue(double z)
{
    if (z != data->zValue) {
        data->zValue = z;
        geometryChanged();
    }
}

Rectangle RenderObject::geometry() const
{
    return data->geometry;
}

void RenderObject::setBackground(Image *image)
{
    assert(image);
    data->background = 0;
    delete data->image;
    data->image = image;
}

bool RenderObject::setBackground(const std::string &path)
{
    if (data->background)
        data->background = 0;
    if (!data->image) {
        data->image = new Image;
    }
    assert(data->image);
    update();
    return data->image->load(path);
}

void RenderObject::setText(const std::string &string)
{
    if (data->text != string) {
        data->text = string;
        update();
    }
}

std::string RenderObject::text() const
{
    return data->text;
}

void RenderObject::setAlignment(unsigned int alignment)
{
    if (data->alignment != alignment) {
        data->alignment = alignment;
        update();
    }
}

unsigned int RenderObject::alignment() const
{
    return data->alignment;
}

// ---- RenderObjectLayout ----

RenderObjectLayout::RenderObjectLayout(LayoutType type)
    : RenderObject(Type_Layout), data(type)
{
}

RenderObjectLayout::~RenderObjectLayout()
{
    for (std::vector<RenderObject*>::const_iterator it = data.renderObjects.begin(); it != data.renderObjects.end(); ++it) {
        RenderObjectLayout *hack = static_cast<RenderObjectLayout*>(*it);
        hack->RenderObject::data->layout = 0;
        delete *it;
    }
}


RenderObjectLayout::LayoutType RenderObjectLayout::layoutType() const
{
    return data.layoutType;
}

const std::vector<RenderObject*> &RenderObjectLayout::renderObjects() const
{
    return data.renderObjects;
}

int RenderObjectLayout::count() const
{
    return data.renderObjects.size();
}

RenderObject * RenderObjectLayout::renderObjectAt(int index) const
{
    assert(index < int(data.renderObjects.size()));
    assert(index >= 0);
    return data.renderObjects.at(index);
}

RenderObject * RenderObjectLayout::takeAt(int index)
{
    assert(index < int(data.renderObjects.size()));
    assert(index >= 0);
    RenderObject *ret = data.renderObjects.at(index);
    data.renderObjects.erase(data.renderObjects.begin() + index);
    return ret;
}

void RenderObjectLayout::insertRenderObject(int index, RenderObject *object)
{
    if (index < 0 || index > int(data.renderObjects.size()))
        index = data.renderObjects.size();
    data.renderObjects.insert(data.renderObjects.begin() + index, object);
}

static inline const double &pick(const Size &size, RenderObjectLayout::LayoutType type)
{
    return type == RenderObjectLayout::VerticalBox ? size.height() : size.width();
}

static inline double &pick(Size &size, RenderObjectLayout::LayoutType type)
{
    return type == RenderObjectLayout::VerticalBox ? size.height() : size.width();
}

static inline const double &pick(const Rectangle &rect, RenderObjectLayout::LayoutType type)
{
    return pick(rect.size(), type);
}

static inline double &pick(Rectangle &rect, RenderObjectLayout::LayoutType type)
{
    return pick(rect.size(), type);
}

void RenderObjectLayout::setGeometry(const Rectangle &rect)
{
    if (data.renderObjects.size() == 0)
        return;
    double total = pick(rect, data.layoutType);
    double needed = 0;
    std::vector<RenderObject*> spacers;
    for (std::vector<RenderObject*>::const_iterator it = data.renderObjects.begin(); it != data.renderObjects.end(); ++it) {
        if ((*it)->type() == Type_Spacer) {
            spacers.push_back(*it);
        } else {
            needed += pick((*it)->geometry(), data.layoutType);
        }
    }

    double spacing = data.spacing;
    double perSpacer = 0;
    const int spacingCount = (data.renderObjects.size() - spacers.size() + 1);
    if (spacers.size() == 0) {
        spacing = max(spacing, (total - needed) / spacingCount);
    } else if (total > needed) {
        perSpacer = (total - needed - (spacingCount * spacing) / spacers.size());
    }

    const LayoutType otherLayoutType = (data.layoutType == HorizontalBox ? VerticalBox : VerticalBox);

    double xy = spacing;
    for (std::vector<RenderObject*>::const_iterator it = data.renderObjects.begin(); it != data.renderObjects.end(); ++it) {
        Rectangle rectangle(Point(0, 0), (*it)->geometry().size());
        pick(rectangle, data.layoutType) = xy;
        pick(rectangle, otherLayoutType) = spacing;
        if ((*it)->type() == Type_Spacer) {
            pick(rectangle, data.layoutType) = perSpacer;
        }
        (*it)->setGeometry(rectangle);
        xy += spacing;
    }
}

double RenderObjectLayout::spacing() const
{
    return data.spacing;
}

void RenderObjectLayout::setSpacing(double val)
{
    data.spacing = val;
    setGeometry(geometry()); // could mark as dirty and update when needed
}

bool RenderObjectLayout::removeRenderObject(RenderObject *object)
{
    for (std::vector<RenderObject*>::iterator it = data.renderObjects.begin(); it != data.renderObjects.end(); ++it) {
        if (*it == object) {
            data.renderObjects.erase(it);
            return true;
        }
    }
    return false;
}

Size RenderObjectLayout::minimumSize() const
{
    if (data.minimumSize.isNull()) {
        const LayoutType otherLayoutType = (data.layoutType == HorizontalBox ? VerticalBox : VerticalBox);
        for (std::vector<RenderObject*>::const_iterator it = data.renderObjects.begin(); it != data.renderObjects.end(); ++it) {
            switch ((*it)->type()) {
            case Type_Spacer:
                break;

            case Type_Layout:
                pick(data.minimumSize, data.layoutType) += pick(static_cast<const RenderObjectLayout*>(*it)->minimumSize(),
                                                                data.layoutType);
                pick(data.minimumSize, otherLayoutType) = max(pick(data.minimumSize, otherLayoutType),
                                                              pick((*it)->geometry(), otherLayoutType));
                break;

            default:
                pick(data.minimumSize, data.layoutType) = max(pick(data.minimumSize, data.layoutType),
                                                              pick((*it)->geometry(), data.layoutType));
                pick(data.minimumSize, otherLayoutType) = max(pick(data.minimumSize, otherLayoutType),
                                                              pick((*it)->geometry(), otherLayoutType));
                break;
            }
        }
    }
    return data.minimumSize;
}
